/**
 * @author Maneesh Chiba <mchiba@higherlogic.com>
 * @copyright 2009-2024 Vanilla Forums Inc.
 * @license GPL-2.0-only
 */

import { IComment } from "@dashboard/@types/api/comment";
import { LayoutEditorPreviewData } from "@dashboard/layout/editor/LayoutEditorPreviewData";
import { UserFixture } from "@library/features/__fixtures__/User.fixture";
import { IThreadItem } from "@vanilla/addon-vanilla/comments/NestedCommentTypes";
import { blessStringAsSanitizedHtml } from "@vanilla/dom-utils";
import { RecordID, uuidv4 } from "@vanilla/utils";
import set from "lodash-es/set";

interface IOptions {
    maxDepth?: number;
    minCommentsPerDepth?: number;
    includeHoles?: boolean;
    collapseChildDepth?: number;
    parentCommentID?: IComment["commentID"];
    randomizeCommentContent?: boolean;
}

interface IRecursiveOptions {
    currentDepth: number;
    path?: string;
}

export class CommentFixture {
    public static mockComment: IComment = {
        commentID: 999999,
        categoryID: 5,
        parentRecordType: "discussion",
        parentRecordID: 999999,
        insertUser: UserFixture.createMockUser({ userID: 1 }),
        insertUserID: 1,
        dateInserted: "2020-10-06T15:30:44+00:00",
        dateUpdated: "2020-10-06T15:30:44+00:00",
        score: 999,
        url: "https://vanillaforums.com/discussion/comment/999999#Comment_999999",
        attributes: {},
        body: blessStringAsSanitizedHtml("This content is generated by users on the site. You can't update it here."),
        name: "This content is generated by users on the site. You can't update it here.",
        reactions: LayoutEditorPreviewData.reactions(),
    };

    public static comment(partial: Partial<IComment>): IComment {
        return {
            ...this.mockComment,
            ...partial,
        };
    }

    public static makeThreadHole = (
        offset: number,
        countAllComments: number,
        countAllInsertUsers: number,
        depth: number,
        parentCommentID: IComment["commentID"] | null,
    ): IThreadItem => {
        return {
            type: "hole",
            offset,
            insertUsers: new Array(5).fill(null).map((_) => UserFixture.createMockUser()),
            countAllComments,
            countAllInsertUsers,
            parentCommentID,
            depth,
            apiUrl: "/api/v2/",
            path: "",
        };
    };

    public static createMockThreadStructure = (
        options?: IOptions,
        recursiveOptions?: IRecursiveOptions,
    ): IThreadItem[] => {
        // Some defaults
        let {
            maxDepth = 3,
            minCommentsPerDepth = 2,
            collapseChildDepth = 3,
            parentCommentID = null,
            includeHoles = false,
        } = options ?? {};

        const { currentDepth = 1 } = recursiveOptions ?? {};

        const allCommentIDs: RecordID[] = [];

        const items: IThreadItem[] = [];

        if (currentDepth < collapseChildDepth) {
            // We have uncollapsed content here.
            for (let i = 0; i < minCommentsPerDepth; i++) {
                const id = uuidv4() as unknown as IComment["commentID"];
                allCommentIDs.push(id);
                const threadItem: IThreadItem = {
                    type: "comment",
                    commentID: id,
                    parentCommentID,
                    depth: currentDepth,
                    path: recursiveOptions?.path ? `${recursiveOptions?.path}.${id}` : `${id}`,
                    children: [],
                };

                if (currentDepth <= maxDepth) {
                    // Let's add some children.
                    threadItem.children = this.createMockThreadStructure(
                        {
                            maxDepth: maxDepth,
                            minCommentsPerDepth,
                            collapseChildDepth,
                            parentCommentID: id,
                            includeHoles,
                        },
                        {
                            currentDepth: currentDepth + 1,
                            path: recursiveOptions?.path,
                        },
                    );
                }
                items.push(threadItem);
            }
        }

        if (includeHoles && currentDepth > 1 && currentDepth <= maxDepth) {
            // Let's add a hole.
            items.push(
                this.makeThreadHole(
                    minCommentsPerDepth + 1,
                    Number(Math.abs(100 / currentDepth).toFixed(0)),
                    Number(Math.abs(30 / currentDepth).toFixed(0)),
                    currentDepth,
                    parentCommentID,
                ),
            );
        }

        return items;
    };

    public static getCommentIDs = (threadStructure: IThreadItem[]): Array<IComment["commentID"]> => {
        return threadStructure.reduce((current, item) => {
            if (item.type === "comment") {
                return [...current, item.commentID, ...this.getCommentIDs(item.children ?? [])];
            } else {
                return current;
            }
        }, [] as Array<IComment["commentID"]>);
    };

    public static randomComments = [
        "Strive not to be a success, but rather to be of value.",
        "You miss 100% of the shots you don't take.",
        "Twenty years from now you will be more disappointed by the things that you didn't do than by the ones you did do, so throw off the bowlines, sail away from safe harbor, catch the trade winds in your sails.  Explore, Dream, Discover.",
        "The best time to plant a tree was 20 years ago. The second best time is now.",
        "You can never cross the ocean until you have the courage to lose sight of the shore.",
        "People often say that motivation doesn't last. Well, neither does bathing.  That's why we recommend it daily.",
        "The only way to do great work is to love what you do.",
        "The best revenge is massive success.",
        "The two most important days in your life are the day you are born and the day you find out why.",
        "The only person you are destined to become is the person you decide to be.",
        "Don't watch the clock; do what it does. Keep going.",
        "The only limit to our realization of tomorrow will be our doubts of today.",
    ];

    public static createMockThreadStructureResponse = (options?: IOptions) => {
        const structure = this.createMockThreadStructure(options);
        let allCommentIDs = this.getCommentIDs(structure);

        const commentsThread = {
            threadStructure: structure,
            commentsByID: Object.fromEntries(
                allCommentIDs.map((id) => [
                    id,
                    this.comment({
                        commentID: id,
                        ...(options?.randomizeCommentContent && {
                            body: blessStringAsSanitizedHtml(
                                this.randomComments[Math.floor(Math.random() * this.randomComments.length)],
                            ),
                        }),
                    }),
                ]),
            ),
        };

        const hasExternalData =
            LayoutEditorPreviewData.externallyRegisteredData?.externalData &&
            Object.values(LayoutEditorPreviewData.externallyRegisteredData.externalData).length > 0;

        if (hasExternalData) {
            const externalData = LayoutEditorPreviewData.externallyRegisteredData.externalData;
            Object.keys(externalData ?? {}).forEach((dataKey) => {
                if (externalData?.[dataKey]) {
                    const adjustedCommentsByID = { ...commentsThread.commentsByID };
                    Object.keys(adjustedCommentsByID).forEach((commentID) => {
                        set(
                            adjustedCommentsByID,
                            `${commentID}.${externalData?.[dataKey].path}`,
                            externalData?.[dataKey].registererData,
                        );
                    });
                    commentsThread.commentsByID = adjustedCommentsByID;
                }
            });
        }

        return commentsThread;
    };
}
